//
// Created by shaowenhan on 2020/3/7.
//

#include "ConfigurableEventListener.h"
#include "ReadParameters.h"

class TestReadParameter: public testing::Test{
public:
    std::string configurationFile = "../../TEST/Configuration.json";
    ParameterForRoutesRelatedProblem parameterForRoutesRelatedProblem;
    TestReadParameter(){
        ReadParameters::obtainParameters(configurationFile, parameterForRoutesRelatedProblem, 20, 3);
    }
};

TEST_F(TestReadParameter, ObtainParametersForGTSControl)
{
    const auto &par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForGTSControl;
    ASSERT_FLOAT_EQ(5, par.minCusNum);
    ASSERT_FLOAT_EQ(20, par.maxCusNum);
    ASSERT_FLOAT_EQ(1.319508, par.changeFactorForCus);

    ASSERT_FLOAT_EQ(1, par.minDepotNum);
    ASSERT_FLOAT_EQ(5, par.maxDepotNum);
    ASSERT_FLOAT_EQ(1.379730, par.changeFactorForDepot);

    ASSERT_FLOAT_EQ(25, par.iteForUpdatingGranularThres);
    ASSERT_TRUE(par.isAllDepotsUsedForAllMoveTypes);
}

TEST_F(TestReadParameter, ObtainParametersForTabu) {
    const auto &attTabuPar = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parForAttTabu;
    ASSERT_EQ(3, attTabuPar.minimalTabuTenure);
    ASSERT_EQ(5, attTabuPar.maximalTabuTenure);
    ASSERT_EQ(TabuWhenOneArcIsTabued, attTabuPar.attTabuCriterion);

    const auto &solTabuPar = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parForSolTabu;

    ASSERT_EQ(5, solTabuPar.hashTableNum);
    ASSERT_EQ(1, solTabuPar.maxVisitTime);
}

TEST_F(TestReadParameter, ObtainParametersForPenaltyFactor)
{
    const auto &par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForPenaltyFactor;
#if defined(MDOVRP)
    ASSERT_EQ((vector<double>{2.5}), par.initialPenaltyFac);
    ASSERT_EQ((vector<double>{0.002}), par.minimalPenaltyFac);
    ASSERT_EQ((vector<double>{120}), par.maximalPenaltyFac);
#elif defined(LRP)
    ASSERT_EQ((vector<double>{2.5, 2.0}), par.initialPenaltyFac);
    ASSERT_EQ((vector<double>{0.002, 0.001}), par.minimalPenaltyFac);
    ASSERT_EQ((vector<double>{120, 1000.0}), par.maximalPenaltyFac);
#elif defined(MDVRP)
    ASSERT_EQ((vector<double>{2.5, 2.0}), par.initialPenaltyFac);
    ASSERT_EQ((vector<double>{0.002, 0.001}), par.minimalPenaltyFac);
    ASSERT_EQ((vector<double>{120, 1000.0}), par.maximalPenaltyFac);
#endif

    ASSERT_FLOAT_EQ(1.18, par.changeFactor);
}

TEST_F(TestReadParameter, ObtainParametersForGTSTermination)
{
    auto &par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForGTSTermination;
    ASSERT_EQ(UnImprovementStepsAndRunningTime, par.gtsTerminateCriterion);
    ASSERT_EQ(10000000, par.maximalUnImprovementSteps);
    ASSERT_EQ(2, par.maximalRunningSteps);
    ASSERT_EQ(200, par.maxTabuSearchRunningTime);
}

TEST_F(TestReadParameter, ObtainParametersForDiversity)
{
    const auto& par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForDiversity;
    const vector<MoveType> expectedRes{TwoOptStar, Relocate2, RelocateB};

    ASSERT_EQ(expectedRes, par.moveTypesUsedInPerturbation);
    ASSERT_FLOAT_EQ(0.000002, par.diversificationIntensity);
    ASSERT_FLOAT_EQ(0.1, par.deteriorationDegree);
    ASSERT_EQ(2000, par.unImprovementStepsForConsiderPerturbation);
    ASSERT_EQ(4, par.perturbationSteps);
    ASSERT_EQ(400, par.umImprovementStepsBackToHistoricalSol);
    ASSERT_EQ(5, par.unImprovementStepsToReverseRoutes);
    ASSERT_EQ(0.5, par.deteriorationDeltaBackToHisSol);
    ASSERT_EQ(-1, par.deteriorationDegreeBackToHisSol);
}

TEST_F(TestReadParameter, ObtainParametersForRoutesPool)
{
    ASSERT_EQ(UpdateInEachLSAndDeleteThemAfterCirculation, parameterForRoutesRelatedProblem.parameterForRoutesPool.routesPoolUpdateMethod);
    ASSERT_EQ(200, parameterForRoutesRelatedProblem.parameterForRoutesPool.routesPoolSize);
    ASSERT_EQ(1000, parameterForRoutesRelatedProblem.parameterForRoutesPool.maxRoutesPoolSize);
    ASSERT_EQ(200, parameterForRoutesRelatedProblem.parameterForRoutesPool.maxRunTimeForInitialRoutesPool);
    ASSERT_EQ(3000, parameterForRoutesRelatedProblem.parameterForRoutesPool.maximalModelRunningTime);

    auto expectedGTSTerminateParametersForInitialRoutesPool = ParameterForGTSTermination{TotalStepsAndRunningTime, 300, 20, 20};
    ASSERT_EQ(expectedGTSTerminateParametersForInitialRoutesPool,
              parameterForRoutesRelatedProblem.parameterForRoutesPool.parameterForGtsTerminationForInitialRoutesPool);
    auto expectedGTSTerminateParametersForImprovement = ParameterForGTSTermination{UmImprovementAndTotalStepsAndRunningTime, 1400, 120, 12};
    ASSERT_EQ(expectedGTSTerminateParametersForImprovement,
              parameterForRoutesRelatedProblem.parameterForRoutesPool.parameterForGtsTerminationForImprovement);

    // routes pool size should not exceed maximal routes pool size
    ReadParameters::obtainParameters(configurationFile, parameterForRoutesRelatedProblem, 200, 2);
    ASSERT_EQ(1000, parameterForRoutesRelatedProblem.parameterForRoutesPool.routesPoolSize);
}

TEST_F(TestReadParameter, ObatinParametersForPathRelinking)
{
    const auto &par = parameterForRoutesRelatedProblem.parameterForHybridAlg.parameterForCrossoverOperators.parameterForPathRelinking;
    ASSERT_FLOAT_EQ(0.5, par.terminationDistanceFactor);
    ASSERT_FLOAT_EQ(0.2, par.putIntoPathSetShortestDisFac);
    ASSERT_EQ(5, par.solNumInEachStep);
    ASSERT_FLOAT_EQ(0.6, par.parameterForDisBetSol.weightOfCusByDifDepot);
}

TEST_F(TestReadParameter, ObtainParametersForSolveStrategy)
{
    ASSERT_EQ(ModelWithLocalSearch, parameterForRoutesRelatedProblem.parameterForSolveStrategy.solveStrategy);
    ASSERT_EQ(10, parameterForRoutesRelatedProblem.parameterForSolveStrategy.runNum);
    ASSERT_EQ(10, parameterForRoutesRelatedProblem.parameterForSolveStrategy.inputRandomSeed.size());
    ASSERT_EQ(5, parameterForRoutesRelatedProblem.parameterForSolveStrategy.realRandomSeed.front());
    for(int i = 0; i < 10; i++)
        DLOG(INFO)<<parameterForRoutesRelatedProblem.parameterForSolveStrategy.realRandomSeed[i];
}

TEST_F(TestReadParameter, ObtainParametersForMoveTypeChoose)
{
    auto parForMoveTypeChoose = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForChooseMoveTypes;
    ASSERT_EQ(VNS, parForMoveTypeChoose.chooseMoveStrategy);
}

TEST_F(TestReadParameter, ObtainParametersForAdaptiveMoveTypeChoose)
{
    auto par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForChooseMoveTypes.parameterForAdaptiveMoveTypeChoose;
    ASSERT_FLOAT_EQ(0.5, par.minimalWeight);
    ASSERT_FLOAT_EQ(5.5, par.maximalWeight);
    ASSERT_FLOAT_EQ(0.2, par.discountFactor);
    ASSERT_FLOAT_EQ(0.8, par.penaltyFactorWhenFeasibleMoveNotExist);
    ASSERT_FLOAT_EQ(10, par.initialPenalty);
    ASSERT_FLOAT_EQ(0.3, par.ratioOfInitialWeightToMaxWeight);
    ASSERT_FLOAT_EQ(0.3, par.speed);
    ASSERT_FALSE(par.isPresetWeightsUsed);
    const vector<double> expectedPresetWeights{0.2*5.5, 0.4*5.5, 0.1*5.5, 0.8*5.5};
    ASSERT_EQ(expectedPresetWeights, par.presetWeights);
    const vector<vector<MoveType>> expectedMoveTypes{
            {Relocate1, TwoOpt},
            {TwoOptStar, Relocate2, RelocateB, RelocateF, Exchange1}};
    ASSERT_EQ(expectedMoveTypes, par.moveTypesOfDifGroup);
    ASSERT_TRUE(par.isWeightClearedAfterResetSol);
}

TEST_F(TestReadParameter, ObtainParametersForVNS)
{
    auto par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForChooseMoveTypes.parameterForVNS;
    vector<int> expectedRes{35, 35, 15, 20};
    ASSERT_EQ(expectedRes, par.cycle);
    ASSERT_TRUE(par.isBackToFirstLevelAfterTraversingAllLevels);
    const vector<vector<MoveType>> expectedMoveTypes{
            {Relocate1, TwoOpt, RelocateWithReverse2},
            {TwoOptStar, Relocate2}};
    ASSERT_EQ(expectedMoveTypes, par.moveTypesInDifLevelOrGroup);
}

TEST_F(TestReadParameter, ObtainParametersForAdaptiveVNS)
{
    auto par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parameterForChooseMoveTypes.parameterForAdaptiveVNS;
    unordered_map<MoveType, vector<pair<int, double>>, EnumClassHash> expectedMoveTypeToLevelPossibility{
            {TwoOpt, vector<pair<int, double>>{{0, 0.5}, {1, 0.2}, {2, 0.3}}},
            {Exchange1, vector<pair<int, double>>{{0, 0.2}, {2, 0.8}}}
    };
    ASSERT_EQ(expectedMoveTypeToLevelPossibility, par.moveTypeToLevelPossibility);
    const vector<vector<MoveType>> expectedMoveTypes{
            {Relocate1, TwoOpt, RelocateWithReverse2},
            {TwoOptStar, Relocate2, RelocateB, RelocateF, Exchange1}};
    ASSERT_EQ(expectedMoveTypes, par.moveTypesInDifLevelOrGroup);
    vector<int> expectedRes{5, 5, 3, 4};
    ASSERT_EQ(expectedRes, par.cycle);
    ASSERT_FALSE(par.isBackToFirstLevelAfterTraversingAllLevels);
}

TEST_F(TestReadParameter, ObatinParametersForPopulation)
{
    auto par = parameterForRoutesRelatedProblem.parameterForHybridAlg.parameterForPopulation;

    auto parForBasic = par.parForInitialSolConstruct;
    auto expectedGTSTerminateParametersForFeasibleSol = ParameterForGTSTermination{UnImprovementSteps, 1000, 200, 20};
    ASSERT_EQ(expectedGTSTerminateParametersForFeasibleSol,parForBasic.parForGTSTerminateWhenObtainFeasibleSol);
    vector<int> expectedSolNumOfDifDiversity{1, 1, 0, 3};
    ASSERT_EQ(expectedSolNumOfDifDiversity, parForBasic.solNumOfDifDiversity);

    auto parForTwoIndividuals = par.parameterForTwoIndividuals;
    ASSERT_EQ(10, parForTwoIndividuals.cycle);
    ASSERT_EQ(3, parForTwoIndividuals.maximalReinitializationCountWhenSolSimilar);
    ASSERT_EQ(20, parForTwoIndividuals.solSimilarityDis);
    ASSERT_TRUE(parForTwoIndividuals.isQualityConsideredToUpdateParent);

    auto parForLargePop = par.parameterForLargePop;
    ASSERT_EQ(5, parForLargePop.populationSize);
    ASSERT_EQ(ReplaceWorstSolWithChildIfChildBetter, parForLargePop.updatePopCriterion);
    ASSERT_EQ(1, parForLargePop.surviveSolNum);
    ASSERT_TRUE(parForLargePop.isCusAssignedToDepotInLastGeneration);
    ASSERT_EQ(10, parForLargePop.unImprovedStepsToEnergizePop);
    ASSERT_TRUE(parForLargePop.isWholePopEvolvedTogether);

    auto parForFitness = parForLargePop.parameterForFitness;
    ASSERT_EQ(1.6, parForFitness.fitnessFactor);
    ASSERT_EQ(averageDis, parForFitness.diversityCriterion);

    ASSERT_EQ(0.7, parForBasic.parForDisBetSol.weightOfCusByDifDepot);
}

TEST_F(TestReadParameter, ObatinParametersForGPX) {
    auto par = parameterForRoutesRelatedProblem.parameterForHybridAlg.parameterForCrossoverOperators.parameterForGPX;
    ASSERT_EQ(route, par.partitionGranularity);
    ASSERT_EQ(minimumInsert, par.assignRestCusCriterion);
    ASSERT_FLOAT_EQ(1.0, par.possibilityOfAssignRestCusToFirstSol);

    auto parForRoute = par.parameterForGpxWhenRouteIsGranularity;
    ASSERT_FLOAT_EQ(0.2, parForRoute.ratioOfCrossoverRoutesToAllRoutes);
    ASSERT_EQ(longestRoute, parForRoute.retainRouteCriterion);
    ASSERT_EQ(3, parForRoute.ratioOfRouteFromFirstSolToOneFromSecondSol);

    auto parForDepot = par.parameterForGpxWhenDepotIsGranularity;
    ASSERT_EQ(2, parForDepot.depotFromFirstSol);
    ASSERT_EQ(1, parForDepot.depotFromSecondSol);
    ASSERT_EQ(maximumCusCover, parForDepot.partitionDepotCriterion);
    ASSERT_EQ(retainDepotInTurn, parForDepot.retainDepotCriterion);
}

TEST_F(TestReadParameter, ObatinParametersForHybridAlg) {
    auto par = parameterForRoutesRelatedProblem.parameterForHybridAlg;

    ASSERT_EQ(1000, par.maxRunningTime);
    auto expectedGTSTerminateParametersForImprovement = ParameterForGTSTermination{UnImprovementStepsAndRunningTime, 300, 240, 2};
    ASSERT_EQ(expectedGTSTerminateParametersForImprovement,par.parameterForGtsTerminateForImprovement);
    ASSERT_EQ(10, par.ratioOfFinalStepsToInitialSteps);
    ASSERT_EQ(alwaysGreedyPartition, par.crossoverStrategy);
    ASSERT_EQ(greedyPartition, par.crossoverOperator);
}

TEST_F(TestReadParameter, ObatinParametersForObtainBestMoveAndGain) {
    auto par = parameterForRoutesRelatedProblem.parameterForGranularTabuSearch.parForObtainBestMove;

    ASSERT_EQ(ParForObtainBestMove::FeasibleAndBetter, par.respiration);
    ASSERT_EQ(ParForObtainBestMove::LS, par.lsStrategy);
    ASSERT_EQ(2, par.cusInEachMove);
    ASSERT_TRUE(par.isTabuInvalidAfterChangeMoveType);
    ASSERT_TRUE(par.isAttTabuUsed);
    ASSERT_TRUE(par.isSolTabuUsed);
}
